﻿/*
 *  PassBag is multi-platform software that securely manage/store passwords
 *  Copyright (C) <2009>  <Hugo Rodrigues PEREIRA>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *	
 *  You can find a copy of the GPLv3 at project <root directory>/gpl.txt
 *
 */

using System;
using System.Text;

namespace PassBag.Logic {
    /// <summary>
    /// Help to generate random password
    /// </summary>
    public class PasswordPolicy {
        public static readonly int MIN_CHARS = 2;
        private const string PASSWORD_CHARS_SPECIAL = "*$-+?_&=!%{}/";

        private int _numberOfDigit;
        private int _numberOfSpecialChars;
        private int _maxLength;
        // to generate better random, use global random for each methods

        /// <summary>
        /// Gets or sets the number of digit.
        /// </summary>
        /// <value>The number of digit.</value>
        public int NumberOfDigit {
            get {
                return _numberOfDigit;
            }
            set {
                _numberOfDigit = value;
            }
        }

        /// <summary>
        /// Gets or sets the number of special chars.
        /// </summary>
        /// <value>The number of special chars.</value>
        public int NumberOfSpecialChars {
            get {
                return _numberOfSpecialChars;
            }
            set {
                _numberOfSpecialChars = value;
            }
        }

        /// <summary>
        /// Gets or sets the length of the max.
        /// </summary>
        /// <value>The length of the max.</value>
        public int MaxLength {
            get {
                return _maxLength;
            }
            set {
                _maxLength = value;
            }
        }

        /// <summary>
        /// default constructor with default password policy initialisation
        /// </summary>
        public PasswordPolicy ()
            : this (3, 2, 8) {
        }

        /// <summary>
        /// this construcor override the default password policy settings.
        /// </summary>
        /// <param name="_numberOfDigit">number of digit ôn the password.</param>
        /// <param name="_numberOfSpecialChars">number of special chars on the password.</param>
        /// <param name="_maxLength">max length of the password.</param>
        public PasswordPolicy (int numberOfDigit, int numberOfSpecialChars, int maxLength) {
            this._numberOfDigit = numberOfDigit;
            this._numberOfSpecialChars = numberOfSpecialChars;
            this._maxLength = maxLength;
        }

        /// <summary>
        /// Generates the password.
        /// </summary>
        /// <returns>The generated password from the policy.</returns>
        public string GeneratePassword () {
            string password = string.Empty;
            // random pass length between min + MIN_CHARS and max pass length
            int passLength = GetRandomNumber (_numberOfDigit + _numberOfSpecialChars + MIN_CHARS, _maxLength);
            int digitCount = _numberOfDigit;
            int pos = 0;
            char[] array = { };

            // first Generate a random string with a specific size
            password = new string (' ', passLength);

            for (int i = 0; i < (_numberOfDigit + _numberOfSpecialChars); i++) {
                pos = GetRandomNumber (0, passLength - 1);
                if (!password[pos].Equals (' ')) {
                    pos = GetNextSpaceIndex (password, pos);
                }

                if (digitCount > 0) { // first all digit
                    digitCount--;
                    array = password.ToCharArray ();
                    array[pos] = GetRandomNumber (0, 9).ToString ().ToCharArray ()[0];
                }
                else {  // then all special chars
                    array = password.ToCharArray ();
                    array[pos] = Convert.ToChar (GetRandomSpecialChar ());
                }
                password = new string (array);
            }

            for (int j = 0; j < passLength; j++) {
                if (password[j].Equals (' ')) {
                    array = password.ToCharArray ();
                    array[j] = Convert.ToChar (GetRandomString (1, false));
                    password = new string (array);
                }
            }

            return password;
        }

        private int GetNextSpaceIndex (string str, int pos) {
            int spacePos = 0;
            try {
                spacePos = str.IndexOf (' ', pos);

                if (spacePos == -1) {
                    spacePos = str.IndexOf (' ');
                    if (spacePos == -1) {
                        spacePos = 0;
                    }
                }
            }
            catch {
                spacePos = 0;
            }

            return spacePos;
        }

        private string GetRandomSpecialChar () {
            return PASSWORD_CHARS_SPECIAL[GetRandomNumber (0, PASSWORD_CHARS_SPECIAL.Length - 1)].ToString ();
        }

        private int GetRandomNumber (int min, int max) {
            Random random = new Random ();
            System.Threading.Thread.Sleep (DateTime.Now.Second);

            return random.Next (min, max);
        }

        private string GetRandomString (int size, bool lowerCase) {
            Random random = new Random ();
            System.Threading.Thread.Sleep (DateTime.Now.Second);

            StringBuilder builder = new StringBuilder ();
            char ch;
            for (int i = 0; i < size; i++) {
                ch = Convert.ToChar (Convert.ToInt32 (Math.Floor (26 * random.NextDouble () + 65)));
                builder.Append (ch);
            }
            if (lowerCase)
                return builder.ToString ().ToLower ();
            return builder.ToString ();
        }
    }
}
